home *** CD-ROM | disk | FTP | other *** search
/ The Programmer Disk / The Programmer Disk (Microforum).iso / xpro / c3 / pro24 / aintr.asm < prev    next >
Assembly Source File  |  1986-08-08  |  15KB  |  483 lines

  1. ;*****************************************************************************
  2. ;        Change Log
  3. ;  Date        | Change
  4. ;-----------+-----------------------------------------------------------------
  5. ; 31-Dec-85 | Created change log
  6. ; 31-Dec-85 | Make sure DS: register is set properly!
  7. ;        | Note: Why the CLD at the start of the routine?  CLI?
  8. ;  1-Jan-86 | Removed CLD.  Roger suggests this was carryover from 6502 code
  9. ;        | where CLD is clear-decimal-mode.
  10. ;        | Change 62H EOI code to 20H EOI code like everything else that
  11. ;        | talks to interrupt chip.    Note that we are tweaking the primary
  12. ;        | interrupt controller chip on a /AT, but that is OK because the
  13. ;        | EOI was sent to the secondary controller via the RE_DIRECT code
  14. ;        | (PC/AT Tech Ref page 5-71)
  15. ;        | This is the same mechanism used on both the /XT and /AT, e.g.
  16. ;        | PC/XT Tech Ref page A-80, lines 5729-5730
  17. ;  5-Feb-86 | Keep interrupts off during interrupt handler
  18. ;  8-Feb-86 | Added code to capture system exclusive messages
  19. ;        | Removed some debugging stores into d0,d1,d2,d3
  20. ;        | Removed interrupt nesting counter -- if interrupts nest, you'll
  21. ;        | crash before you can print the error report
  22. ; 13-Feb-86 | Changed DATA macro to a more sane name: GETMIDI
  23. ; 14-Feb-86 | Moved all variables to DSEG (they were in PSEG -- why did this
  24. ;        | ever work before?)
  25. ;  5-May-86 | Optimized input for better transcription speed
  26. ;  9-Jul-86 | Added loop to avoid exiting interrupts with more data available
  27. ; 18-Jul-86 | Fixed a running status bug and cleaned up some debugging code
  28. ;*****************************************************************************
  29. ;;
  30. ;; MPU-401 interrupt handler
  31. ;;    modelled after MPU-401 manual, pages 55-56
  32. ;;    except that Ack commands are handled by setting
  33. ;;    a flag and other commands are handled by
  34. ;;    putting data into a buffer.  Once things are
  35. ;;    initialized, this is the only place that should
  36. ;;    read data from the MPU-401.  All writes (commands)
  37. ;;    are issued from C routines.
  38. ;;
  39. ;; Notes:  (Joe Newcomer, 31-Dec-85)
  40. ;;   Because an interrupt can occur from anywhere, including DOS and
  41. ;;   the BIOS, we cannot, repeat CANNOT assume the validity of any
  42. ;;   register except CS:.  In particular, SS:SP is quite possibly a
  43. ;;   BIOS stack segment which are infinitesmally small.     We CANNOT
  44. ;;   push anything onto the BIOS stack segment without risking severe
  45. ;;   damage to the integrity of the system.  So we have here a large
  46. ;;   private stack segement; we switch attention to it, *very carefully*
  47. ;;   save our state on it, and then call the code which handles our
  48. ;;   MPU-401 interrupt.     Upon return, we *very carefully* reset the stack
  49. ;;   and return to our caller.    Since we need to address the C data segment,
  50. ;;   we must also load DS:, which we need to set intnest and various buffer
  51. ;;   headers.  See the note associated with the setting of DS:; this
  52. ;;   code works only in the small data model.
  53.  
  54. include dos.mac
  55.  
  56. ; DEBUG = 1        ;; define DEBUG to enable some extra record keeping
  57.  
  58. DSEG
  59. extrn interror:word    ;; report errors up to C handlers
  60. extrn timeerr:word    ;; reports timeout errors to C handlers
  61. extrn time_req:word    ;; set to true if next Ack will be timing byte
  62.  
  63. IFDEF DEBUG
  64. extrn loop_cnt:word    ;; count loop interations
  65. extrn loop_max:word    ;; max value of loop interations
  66. extrn intcnt:word    ;; count of interrupts taken
  67. ENDIF
  68.  
  69. extrn buff:byte        ;; data from mpu401
  70. extrn buffhead:word    ;; data is removed from head offset
  71. extrn bufftail:word    ;; data is inserted at the tail offset
  72.  
  73. extrn xbuff:word    ;; system exclusive buffer pointer
  74. extrn xbuffhea:word
  75. extrn xbufftai:word
  76. extrn xbuffmas:word
  77.  
  78. ;;
  79. ;; Globals used in communication with mpu.c
  80. ;;
  81.  
  82. extrn    Ack:word        ;; set if ack received
  83. extrn    Unknown:word        ;; set for unknown command (for debugging)
  84. extrn    Ticks:dword        ;; Clock ticks (400 = 1 second)
  85.  
  86. ;;Midi information
  87. extrn MidiTime:byte    ;; extra timing byte
  88. extrn MidiStat:byte    ;; Running status
  89. extrn Midi1:byte    ;; First arg
  90. extrn Midi2:byte    ;; Second arg
  91. extrn Midi3:byte    ;; Third arg (not used)
  92.  
  93. extrn    rd_delay:word    ;; counts down wait for mpu data
  94.  
  95. ENDDS
  96.  
  97. pseg
  98. public a_intr, init_asm
  99.  
  100. ; These must be in the pseg because on entry only the CS: is addressible
  101.  
  102. DASEG    DW    0
  103.  
  104. OldAX    DW    ?
  105. OldSS    DW    ?        ; old stack segment
  106. OldSP    DW    ?        ; old stack pointer
  107.     DW    512 DUP(?)    ; local stack space for intercept routine
  108. STACK    label    WORD
  109.  
  110.  
  111. NESTERR = 1        ;;nested interrupt error
  112. BUFFERR = 2        ;;input buffer overflow error
  113. CMDERR = 3        ;;unknown command
  114. TIMEOUT = 4        ;;timeout waiting to read data
  115.  
  116. BUFFMASK = 3FFH        ;; buffer size is 1024 bytes, 3FF=1023
  117.  
  118. ;; Status byte masks
  119. ;;
  120.     DRR    =    40h    ;; Data Receive Ready
  121.     DSR    =    80h    ;; Data Send Ready
  122.  
  123.     STATPORT = 331H        ;; MPU-401 Status port
  124.     DATAPORT = 330H        ;; MPU-401 Data (MPU to PC) port
  125.  
  126. ;*****************************************************************************
  127. ; init_asm(): called to save the data segment into a place where
  128. ;          the interupt routine can get at it.
  129.  
  130. init_asm proc near
  131.  
  132.     push    bp        ;save bp
  133.     mov    bp,sp        ;move sp into bp
  134.     mov    cs:daseg,ds    ;save the ds in daseg
  135.     pop    bp
  136.     ret
  137.  
  138. init_asm    endp
  139.  
  140. ;*****************************************************************************
  141. ;                    a_intr
  142. ; Called via:
  143. ;    far call from interrupt handler.  NOTE: proc is declared 'near' so
  144. ;    that funny fixups are not required when linking it into C small model
  145. ;    code.  Since we return via IRET, the near/far distinction does not
  146. ;    matter.     HOWEVER if one were to play funny games with doing returns
  147. ;    and twiddling flags (unlikely) the near/far distinction would matter
  148. ;*****************************************************************************
  149. a_intr    proc    near
  150. ; Establish a stack for us
  151.     mov    OldSS,SS    ; save old stack
  152.     mov    OldSP,SP    ; ...
  153.     mov    OldAX,AX    ; and scratch register
  154.     cli            ; don't play with fire, turn 'em off
  155.     mov    AX,CS        ; our new stack segment is addressible by CS:
  156.     mov    SS,AX        ; ..
  157.     mov    SP,offset STACK ; always change SS,SP in adjacent instructions
  158.  
  159.                 ; In principle, we didn't need to turn 
  160.                 ; interrupts off because doing it in that 
  161.                 ; order guarantees that no interrupt will 
  162.                 ; occur between mov SS and mov SP, but early
  163.                 ; 8088s had a bug and it didn't work.
  164.                 ; Better safe than sorry.  An /XT could be
  165.                 ; repaired with one of these bogus chips
  166.  
  167. ;    sti            ; allow interrupts again
  168. ; Save state
  169.     push    ds        ; save state
  170.     push    es
  171.     push    ax
  172.     push    bx
  173.     push    cx
  174.     push    dx
  175.     push    di
  176.     push    si
  177.     push    ds
  178. ; begin body
  179.  
  180. ;    Restore DS from value  saved in pgroup:daseg.
  181.     mov    bx,offset pgroup:daseg
  182.     mov    ds,cs:[bx]    ; now DS has the offset of dgroup segment
  183.     assume    ds:dgroup
  184.  
  185. ;    mov    ax,DGROUP
  186. ;    mov    ax,SEG intnest    ; make DS be correct
  187.                 ; note: All variables have the same DS
  188.                 ; so doing it for one will do it for
  189.                 ; all
  190.                 ; This trick will not work in the large
  191.                 ; memory model; there we have to load
  192.                 ; DS: for each variable, because they
  193.                 ; could be in different segments
  194.                 ; No, I don't know how to handle the
  195.                 ; case where a long vector falls across
  196.                 ; a segment boundary
  197.  
  198. ; at this point we may now validly address data
  199. IFDEF DEBUG
  200.     inc    intcnt        ; up interrupt count
  201.     mov    loop_cnt, 0    ; initialize iteration counter
  202. ENDIF
  203.  
  204. readit:    call    mpu_aintr
  205. ; end body
  206. IFDEF DEBUG
  207.     inc    loop_cnt
  208. ENDIF
  209.     mov    al,20h        ;;; EOI code
  210.     out    20h,al        ;;; Announce end of interrupt
  211.     mov    dx,STATPORT    ;; load port number
  212.     in    al,dx        ;; read in char from port    
  213.     test    al,DSR        ;
  214.     jz    readit        ;loop to handle next data byte
  215. ;;                See note about the fact that we are
  216. ;;                twiddling the primary interrupt controller
  217. ;;                chip on an /AT, but this is no different
  218. ;;                than what is required on the /XT
  219. IFDEF DEBUG
  220.     mov    ax, loop_cnt    ;; loop_max = max(loop_max, loop_cnt)
  221.     cmp    ax, loop_max
  222.     jb    leave
  223.     mov    loop_max, ax
  224. ENDIF
  225. leave:    pop    ds
  226.     pop    si
  227.     pop    di
  228.     pop    dx
  229.     pop    cx
  230.     pop    bx
  231.     pop    ax
  232.     pop    es
  233.     pop    ds
  234. ; Now restore our old stack
  235.     cli            ; do it safely...
  236.     mov    SS,OldSS    ; restore SS
  237.     mov    SP,OldSP    ; restore SP
  238. ;    sti            ; allow them again
  239.     mov    AX,OldAX    ; restore AX
  240.     iret
  241. a_intr    endp
  242.  
  243. ;;
  244.  
  245. ;;
  246. ;; Data from mpu-401
  247. ;;
  248.  
  249.     MPU_ACK        =    0feh    ;; acknowledgment of end of command
  250.     ABOVE_TIMING_BYTE =    0f0h    ;; 1st value greater than legal timing
  251.                     ;;  byte values (0 - 0efh)
  252.     TIMER_OVERFLOW    =    0f8h    ;; record timer reached 240
  253.     TIMER_INCR    =    240d    ;; add when TIMER_OVERFLOW comes
  254.     SYSTEM_MESSAGE    =    0ffh    ;; MIDI system message
  255.     MIDI_EXCLUSIVE    =    0f0h    ;; MIDI exclusive message
  256.     MIDI_EOX    =    0f7h    ;; MIDI EOX (end of MIDI exclusive)
  257.     MPUNOOP        =    0f8h    ;; MPU Mark: No Operation
  258.  
  259. ;;
  260. ;; midi codes
  261. ;; high order 4 bits (of 8) give command
  262. ;; low order 4 bits give midi channel number
  263. ;;
  264.     MCOMMASK    =    0f0h    ;; These bits give MIDI command
  265.  
  266.     MSTATUSMASK    =    080h    ;; This bit set if MIDI status byte
  267.     MCHANMASK    =    00fh    ;; These bits give MIDI channel number
  268.  
  269.     NOTEOFF        =    080h    ;; status,pitch,veloc
  270.     NOTEON        =    090h    ;; status,pitch,veloc (=0 means off)
  271.     NOTEAFTERTOUCH    =    0a0h    ;; status,pitch,arg2
  272.     CONTROLCHANGE    =    0b0h    ;; status,arg1,arg2
  273.     PROGRAMCHANGE    =    0c0h    ;; status,program
  274.     CHAFTERTOUCH    =    0d0h    ;; status,arg
  275.     PITCHWHEEL    =    0e0h    ;; status,arg1,arg2
  276.     MPUCOM        =    0f0h    ;; fake midi command, really mpu401
  277.     
  278. MAXDELAY = 20000    ;; mpu_get times out after this many tries
  279.  
  280.  
  281. ;*****************************************************************************
  282. ;                    mpu_get
  283. ;
  284. ;*****************************************************************************
  285. mpu_get proc near        ;; read data from mpu 401
  286.     mov rd_delay,MAXDELAY
  287. tryagain:
  288.     mov    dx,STATPORT    ;; read status port
  289.     in    al,dx
  290.     test    al,DSR        ;; data ready to send?
  291.     jz    gotit        ;;   yes - read the data
  292.     dec    rd_delay    ;;   no - test for timeout
  293.     jnz    tryagain    ;; timed out? no - repeat
  294.     mov    timeerr,TIMEOUT ;;  yes - report error,
  295.     mov    al,0f8h        ;;  and return innocuous (I hope) data
  296.     ret
  297. gotit:    mov    dx,DATAPORT    ;; load port number
  298.     in    al,dx        ;; read in char from port    
  299.     ret
  300. mpu_get endp
  301.  
  302.  
  303. ;*****************************************************************************
  304. ;                    putbuf
  305. ;*****************************************************************************
  306. putbuf proc near        ;; put data into buffer
  307.     mov    dx,bufftail
  308.     add    dx,4
  309.     and    dx,BUFFMASK    ;; wrap around ( dx = dx mod buffersize )
  310.     cmp    dx,buffhead
  311.     je    bufferfull
  312. ;; save new bufftail in dx, copy bytes
  313.     mov    si,bufftail
  314.     mov    bl, MidiStat
  315.     mov    byte ptr buff[si],bl
  316.     inc    si
  317.     mov    bl, Midi1
  318.     mov    byte ptr buff[si],bl
  319.     inc    si
  320.     mov    bl,Midi2
  321.     mov    byte ptr buff[si],bl
  322.     mov    bufftail,dx
  323.     ret
  324. bufferfull:
  325.     mov    interror,BUFFERR
  326.     ret
  327. putbuf endp
  328.  
  329. GETMIDI macro ;; read the mpu 401 data port into al
  330.         call    mpu_get
  331.     endm
  332.  
  333.  
  334. ;*****************************************************************************
  335. ;                   mpu_aintr
  336. ;*****************************************************************************
  337. mpu_aintr proc    near
  338.  
  339.     GETMIDI 1,gm1        ;; get what 401 want us to get
  340.  
  341.     mov    ah,0            ;; several places assume ax = al
  342.     cmp    ax,ABOVE_TIMING_BYTE    ;; Timing byte?
  343.     jb    l_timing_byte        ;; (usually followed by midi data)
  344.     cmp    al, TIMER_OVERFLOW
  345.     je    l_timer_overflow
  346.     cmp    al,MPU_ACK        ;; Ack?
  347.     je    l_mpu_ack
  348.     cmp    al,SYSTEM_MESSAGE    ;; Midi system message?
  349.     jne    bad
  350.     jmp    l_system_message
  351.  
  352.  
  353. ;;
  354. ;; This routine does not handle:
  355. ;;    Track data requests
  356. ;;    Conductor requests
  357. ;;    Clock to host
  358. ;; musicinit() initializes the MPU-401 in such a way so that these bytes
  359. ;; are never sent.  If they do appear, they end up here.
  360. ;;
  361.  
  362. bad:
  363.     mov    Unknown,ax
  364.     mov    interror,CMDERR
  365.     jmp    bye
  366. ;;
  367. ;; Handle each class of 401 message
  368. ;;
  369.  
  370. ;; An ack, set Ack so that mpu_wait() can see it.
  371. l_mpu_ack:
  372.     inc    Ack
  373.     cmp    time_req, 0    ;; Does this command return timing data?
  374.     je    ack_done    ;; if not, just return
  375.     GETMIDI 2,gm2        ;; otherwise, read one more byte
  376.     mov    ah, 0        ;; increment Ticks by result
  377.     add    WORD PTR Ticks, ax
  378.     adc    WORD PTR Ticks+2, 0
  379.     mov    time_req, 0
  380. ack_done:
  381.     jmp    bye
  382.  
  383. ;; A timer overflow, increment clock by appropriate number of ticks
  384. l_timer_overflow:
  385.     add    WORD PTR Ticks,TIMER_INCR ;; yes, do 32 bit incr of clock
  386.     adc    WORD PTR Ticks+2,0
  387.     jmp    bye
  388.  
  389. ;; A timing byte - the hard case
  390. ;; There are a number of possibilities, on which we branch
  391. l_timing_byte:
  392.     mov    MidiTime,al        ;; save timing byte
  393.     add    WORD PTR Ticks,ax    ;; yes, do 32 bit incr of clock
  394.     adc    WORD PTR Ticks+2,0
  395.     GETMIDI 3,gm3            ;; get next byte
  396.     test    al,MSTATUSMASK        ;; It's midi, is it a status byte?
  397.     je    runstat
  398.  
  399. ;; Here we have new midi status byte.  Stash it and read in first data
  400.  
  401.     mov    MidiStat,al
  402.     mov    bl,al            ;; copy command to bl
  403.     
  404.     and    bl,MCOMMASK        ;; "And" off channel bits
  405.     cmp    bl,MPUCOM        ;; Is it an MPU command in disguise?
  406.     je    l_mpucom        ;; Yes, deal with it.
  407.  
  408.     GETMIDI 4,gm4            ;; read in first data byte
  409.     jmp    decode            ;; decide whether 1 or 2 data bytes
  410.  
  411. runstat:
  412.     mov    bl,MidiStat        ;; no, use previous (running) status
  413.     and    bl,MCOMMASK
  414.  
  415. ;; Commands 0c0h (program change) and 0d0h (channel after touch) have 2 bytes
  416. ;; at this point, al has 1st data byte, bl has upper four bits of status byte
  417.  
  418. decode:    mov    Midi1,al        ;; save first data byte
  419.     cmp    bl,CHAFTERTOUCH
  420.     je    gotmsg
  421.     cmp    bl,PROGRAMCHANGE
  422.     je    gotmsg
  423.  
  424.     GETMIDI 5,gm5            ;; read second data byte
  425.     mov    Midi2,al        ;; save second data bytes
  426. gotmsg:
  427.  
  428. ;;
  429. ;; Here the midi command is contained in the (2 or) 3 bytes
  430. ;; MidiStat, Midi1, and Midi2
  431. ;;
  432.     call    putbuf        ;; put the data in the buffer
  433.                 ;; optimization note: only one call to putbuf
  434. gobye:    jmp    bye
  435.  
  436.  
  437. ;;
  438. ;; MPU-401 marks 
  439. ;;    These shouldn't happen and are ignored.  The NOOP mark IS sent
  440. ;;    when recording, contrary to the MPU401 manual.  Since it seems
  441. ;;    harmless, no error is reported if a NOOP is sent.  Otherwise,
  442. ;;    report a bad command with the timing byte in the high-order byte
  443. ;;    of the error data (Unknown) to distinguish the data as mark data.
  444. ;;
  445. l_mpucom:
  446.     cmp    al,MPUNOOP
  447.     je    gobye        ;; MPU-401 manual is wrong!  The 
  448.     mov    ah,MidiTime    ;; report two bytes as unknown
  449.     jmp    bad
  450.  
  451. ;; A MIDI system message, currently only read sys. exclusive messages
  452. l_system_message:
  453.     ;; see what the message is
  454.     GETMIDI 6,gm6
  455.     cmp    al,MIDI_EXCLUSIVE
  456.     je    store_x
  457.     jmp    bad            ;; only handle MIDI_EXCLUSIVE
  458.  
  459. store_x:    ;; put data in buffer until MIDI_EOX read    
  460.     mov    bx,xbuff        ;; do not store if xbuff is NULL
  461.     cmp    bx,0
  462.     je    nobuff
  463.  
  464.     add    bx,xbufftai        ;; add index
  465.     mov    byte ptr [bx],al    ;; and store midi data
  466.     mov    dx,xbufftai        ;; increment with wrap-around
  467.     add    dx,1
  468.     and    dx,xbuffmas
  469.     mov    xbufftai,dx
  470. nobuff:    test    al,MSTATUSMASK        ;; are we done? 
  471.     je    ex_continue        ;; stop on any status byte ...
  472.     cmp    al,MIDI_EXCLUSIVE    ;; ... except midi exclusive
  473.     jne    bye
  474. ex_continue:
  475.     GETMIDI 7,gm7
  476.     jmp    store_x
  477. ;; common return point
  478.     
  479. bye:    ret
  480. mpu_aintr endp
  481.     endps
  482.     end
  483.